// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.Diagnostics;

using Mono.Cecil;
using Mono.Collections.Generic;

namespace Mono.Linker.Steps
{
    struct CategorizedSubSteps
    {
        public List<ISubStep> on_assemblies;
        public List<ISubStep> on_types;
        public List<ISubStep> on_fields;
        public List<ISubStep> on_methods;
        public List<ISubStep> on_properties;
        public List<ISubStep> on_events;
    }

    //
    // Generic steps dispatcher is intended to by used by custom ILLink step which
    // consist of multiple steps. It simplifies their implementation as well as the
    // way how to hook them into the pipeline of existing steps.
    //
    public abstract class SubStepsDispatcher : IStep
    {
        readonly List<ISubStep> substeps;

        CategorizedSubSteps? categorized;
        CategorizedSubSteps Categorized
        {
            get
            {
                Debug.Assert(categorized.HasValue);
                return categorized.Value;
            }
        }

        protected SubStepsDispatcher()
        {
            substeps = new List<ISubStep>();
        }

        protected SubStepsDispatcher(IEnumerable<ISubStep> subSteps)
        {
            substeps = new List<ISubStep>(subSteps);
        }

        public void Add(ISubStep substep)
        {
            substeps.Add(substep);
        }

        public virtual void Process(LinkContext context)
        {
            InitializeSubSteps(context);

            BrowseAssemblies(context.GetAssemblies());
        }

        static bool HasSubSteps(List<ISubStep> substeps) => substeps?.Count > 0;

        void BrowseAssemblies(IEnumerable<AssemblyDefinition> assemblies)
        {
            foreach (var assembly in assemblies)
            {
                CategorizeSubSteps(assembly);

                if (HasSubSteps(Categorized.on_assemblies))
                    DispatchAssembly(assembly);

                if (!ShouldDispatchTypes())
                    continue;

                BrowseTypes(assembly.MainModule.Types);
            }
        }

        bool ShouldDispatchTypes()
        {
            return HasSubSteps(Categorized.on_types)
                || HasSubSteps(Categorized.on_fields)
                || HasSubSteps(Categorized.on_methods)
                || HasSubSteps(Categorized.on_properties)
                || HasSubSteps(Categorized.on_events);
        }

        void BrowseTypes(Collection<TypeDefinition> types)
        {
            foreach (TypeDefinition type in types)
            {
                DispatchType(type);

                if (type.HasFields && HasSubSteps(Categorized.on_fields))
                {
                    foreach (FieldDefinition field in type.Fields)
                        DispatchField(field);
                }

                if (type.HasMethods && HasSubSteps(Categorized.on_methods))
                {
                    foreach (MethodDefinition method in type.Methods)
                        DispatchMethod(method);
                }

                if (type.HasProperties && HasSubSteps(Categorized.on_properties))
                {
                    foreach (PropertyDefinition property in type.Properties)
                        DispatchProperty(property);
                }

                if (type.HasEvents && HasSubSteps(Categorized.on_events))
                {
                    foreach (EventDefinition @event in type.Events)
                        DispatchEvent(@event);
                }

                if (type.HasNestedTypes)
                    BrowseTypes(type.NestedTypes);
            }
        }

        void DispatchAssembly(AssemblyDefinition assembly)
        {
            foreach (var substep in Categorized.on_assemblies)
            {
                substep.ProcessAssembly(assembly);
            }
        }

        void DispatchType(TypeDefinition type)
        {
            foreach (var substep in Categorized.on_types)
            {
                substep.ProcessType(type);
            }
        }

        void DispatchField(FieldDefinition field)
        {
            foreach (var substep in Categorized.on_fields)
            {
                substep.ProcessField(field);
            }
        }

        void DispatchMethod(MethodDefinition method)
        {
            foreach (var substep in Categorized.on_methods)
            {
                substep.ProcessMethod(method);
            }
        }

        void DispatchProperty(PropertyDefinition property)
        {
            foreach (var substep in Categorized.on_properties)
            {
                substep.ProcessProperty(property);
            }
        }

        void DispatchEvent(EventDefinition @event)
        {
            foreach (var substep in Categorized.on_events)
            {
                substep.ProcessEvent(@event);
            }
        }

        void InitializeSubSteps(LinkContext context)
        {
            foreach (var substep in substeps)
                substep.Initialize(context);
        }

        void CategorizeSubSteps(AssemblyDefinition assembly)
        {
            categorized = new CategorizedSubSteps
            {
                on_assemblies = new List<ISubStep>(),
                on_types = new List<ISubStep>(),
                on_fields = new List<ISubStep>(),
                on_methods = new List<ISubStep>(),
                on_properties = new List<ISubStep>(),
                on_events = new List<ISubStep>()
            };

            foreach (var substep in substeps)
                CategorizeSubStep(substep, assembly);
        }

        void CategorizeSubStep(ISubStep substep, AssemblyDefinition assembly)
        {
            if (!substep.IsActiveFor(assembly))
                return;

            CategorizeTarget(substep, SubStepTargets.Assembly, Categorized.on_assemblies);
            CategorizeTarget(substep, SubStepTargets.Type, Categorized.on_types);
            CategorizeTarget(substep, SubStepTargets.Field, Categorized.on_fields);
            CategorizeTarget(substep, SubStepTargets.Method, Categorized.on_methods);
            CategorizeTarget(substep, SubStepTargets.Property, Categorized.on_properties);
            CategorizeTarget(substep, SubStepTargets.Event, Categorized.on_events);
        }

        static void CategorizeTarget(ISubStep substep, SubStepTargets target, List<ISubStep> list)
        {
            if (!Targets(substep, target))
                return;

            list.Add(substep);
        }

        static bool Targets(ISubStep substep, SubStepTargets target) => (substep.Targets & target) == target;
    }
}
